a tool for shared writing and social publishing
at update/reader 147 lines 3.8 kB view raw
1import { z } from "zod"; 2import { 3 PullRequest, 4 PullResponseV1, 5 VersionNotSupportedResponse, 6} from "replicache"; 7import type { Fact } from "src/replicache"; 8import { FactWithIndexes } from "src/replicache/utils"; 9import type { Attribute } from "src/replicache/attributes"; 10import { makeRoute } from "../lib"; 11import type { Env } from "./route"; 12import type { Json } from "supabase/database.types"; 13 14// First define the sub-types for V0 and V1 requests 15const pullRequestV0 = z.object({ 16 pullVersion: z.literal(0), 17 schemaVersion: z.string(), 18 profileID: z.string(), 19 cookie: z.any(), // ReadonlyJSONValue 20 clientID: z.string(), 21 lastMutationID: z.number(), 22}); 23 24// For the Cookie type used in V1 25const cookieType = z.union([ 26 z.null(), 27 z.string(), 28 z.number(), 29 z 30 .object({ 31 order: z.union([z.string(), z.number()]), 32 }) 33 .and(z.record(z.string(), z.any())), // ReadonlyJSONValue with order property 34]); 35 36const pullRequestV1 = z.object({ 37 pullVersion: z.literal(1), 38 schemaVersion: z.string(), 39 profileID: z.string(), 40 cookie: cookieType, 41 clientGroupID: z.string(), 42}); 43 44// Combined PullRequest type 45const PullRequestSchema = z.union([pullRequestV0, pullRequestV1]); 46 47export const pull = makeRoute({ 48 route: "pull", 49 input: z.object({ pullRequest: PullRequestSchema, token_id: z.string() }), 50 handler: async ({ pullRequest, token_id }, { supabase }: Env) => { 51 let body = pullRequest; 52 if (body.pullVersion === 0) return versionNotSupported; 53 let { data, error } = await supabase.rpc("pull_data", { 54 token_id, 55 client_group_id: body.clientGroupID, 56 }); 57 if (!data) { 58 console.log(error); 59 60 return { 61 error: "ClientStateNotFound", 62 } as const; 63 } 64 65 let facts = data.facts as { 66 attribute: string; 67 created_at: string; 68 data: any; 69 entity: string; 70 id: string; 71 updated_at: string | null; 72 version: number; 73 }[]; 74 let publication_data = data.publications as { 75 description: string; 76 title: string; 77 tags: string[]; 78 cover_image: string | null; 79 preferences: Json | null; 80 }[]; 81 let pub_patch = publication_data?.[0] 82 ? [ 83 { 84 op: "put", 85 key: "publication_description", 86 value: publication_data[0].description, 87 }, 88 { 89 op: "put", 90 key: "publication_title", 91 value: publication_data[0].title, 92 }, 93 { 94 op: "put", 95 key: "publication_tags", 96 value: publication_data[0].tags || [], 97 }, 98 { 99 op: "put", 100 key: "publication_cover_image", 101 value: publication_data[0].cover_image || null, 102 }, 103 { 104 op: "put", 105 key: "post_preferences", 106 value: publication_data[0].preferences || null, 107 }, 108 ] 109 : []; 110 111 let clientGroup = ( 112 (data.client_groups as { 113 client_id: string; 114 client_group: string; 115 last_mutation: number; 116 }[]) || [] 117 ).reduce( 118 (acc, clientRecord) => { 119 acc[clientRecord.client_id] = clientRecord.last_mutation; 120 return acc; 121 }, 122 {} as { [clientID: string]: number }, 123 ); 124 125 return { 126 cookie: Date.now(), 127 lastMutationIDChanges: clientGroup, 128 patch: [ 129 { op: "clear" }, 130 { op: "put", key: "initialized", value: true }, 131 ...(facts || []).map((f) => { 132 return { 133 op: "put", 134 key: f.id, 135 value: FactWithIndexes(f as unknown as Fact<Attribute>), 136 } as const; 137 }), 138 ...pub_patch, 139 ], 140 } as PullResponseV1; 141 }, 142}); 143 144const versionNotSupported: VersionNotSupportedResponse = { 145 error: "VersionNotSupported", 146 versionType: "pull", 147};